Certificación en Ciencia de datos

Módulo 3 - Manipulación de datos con tidyverse

Natalia da Silva

2024 -Módulo 3

¿Qué vimos?

Capítulo 3 de r4ds 2da edición, 2023

Transformación de datos con dplyr:

  • Introduce una forma de trabajo que hace relativamente más sencillo trabajar con datos

  • Principal funcionalidad es un conjunto de verbos que facilitan la transformación de datos. Qué se pueden agrupar según donde se realizan las operaciones.

¿Qué clases de objeto retorna dplyr?

Más dplyr, case_match()

Queremos recodificar class, usamos case_match()

mpg  |> 
  select(class) |> 
  table()

 2seater compact  midsize  minivan  pickup subcompact   suv 
  5         47      41        11      33         35      62 

Más dplyr, case_match()

case_match() usa valores para comparar con los de la variable seleccionada

Para caracteres se recodifica directamente con el nombre

library(tidyverse)
mpg |> select(class) |> 
  mutate(class = case_match(class, 
                            "2seater" ~ "2se", 
                         "compact" ~ "co",
                         "midsize" ~ "mid",
                        "minivan" ~ "mini", 
                        "pickup" ~ "pi",
                        "subcompact" ~ "sub",
                        "suv" ~ "su"))|>
  select(class) |> 
  table()
class
 2se   co  mid mini   pi   su  sub 
   5   47   41   11   33   62   35 

Más dplyr, case_match()

mpg |>
  select(cyl) |> 
  table()

 4  5  6  8 
81  4 79 70
mpg |> 
  select(cyl) |> 
  mutate(cyl = case_match(cyl, 
                        4 ~ 3, 
                        5~ 3,
                        6 ~ 7,
                        8~ 5)) |>
select(cyl) |> 
table()
 3  5  7 
85 70 79 

Más dplyr, case_match()

mpg |> 
  select(cyl) |> 
  mutate(cyl = case_match(cyl, 
                       c(4, 5) ~ 3, 
                        6 ~ 7,
                        8 ~ 5)) |>
  select(cyl) |> 
  table()

3  5  7 
85 70 79 

Más dplyr, case_when()

case_when() usa expresiones lógicas del lado izquierdo de la fórmula

mpg |> 
  select(cyl) |> 
  mutate(cyl = case_when( 
                        cyl %in% c(4, 5) ~ 3, 
                        cyl %in% 6 ~ 7,
                        cyl %in% 8~ 5)) |>
select(cyl) |> 
table()

 3  5  7 
85 70 79 

slice()

Selecciona filas por su posición

mpg |> 
  slice(n())

En este caso selecciona la última fila.

¿Qué hace el siguiente código?

mpg |> 
  slice(7:9)

Más slice()

  • slice_head() y slice_tail() seleccionan la primera y la última fila

  • slice_min() y slice_max() seleccionan filas con el valor más grande o chico en una variable

  • slice_sample() selecciona filas aleatoriamente

Más dplyr

  • Muchas veces quisieramos realizar operaciones sobre una selección de variables que cumplen una determinada condición.

  • Ejemplo: realizar una operación sobre todas las variables numéricas, o a todas los nombres de la variables de fecha agregarles _Date al final, o calcular medidas de resumen sólo sobre determinadas variables, etc.

Más dplyr, across()

  • Operar en múltiples columnas al mismo tiempo mediante across()

  • across() hace sencillo aplicar la misma transformación a muchas columnas usando la misma semántica que select() dentro de funciones como summarise() y mutate()

  • across tiene dos argumentos básicos:

    • las columnas que queremos seleccionar .cols, se pueden seleccionar variables por nombre, posición o tipo (tipo el usado en select())
    • Segundo argumento de across() es .fns, que es una función o lista de ellas que se aplican a cada columna. Puede usarse con estilo fórmula

Más dplyr, across()

mtcars |> 
  group_by(cyl, disp) |>
  summarise(
    mwt = mean(wt),
    mmpg = mean(mpg),
    mdrat = mean(drat)
  ) 

Equivalente usando across y variables seleccionadas por nombre

mtcars |> 
  group_by(cyl, disp) |>
  summarise( across(.cols = c(wt,mpg,drat), .fns = mean))

Más dplyr, across()

mtcars |> 
  group_by(cyl, disp) |>
  summarise(across(.cols = c(wt,mpg,drat), .fns = mean)
  )

Equivalente usando across y variables seleccionadas por posición

mtcars |> 
  group_by(cyl, disp) |>
  summarise(across(.cols = c(6, 1, 5), .fns = mean)
  )

Más dplyr, across()

mtcars |> 
  group_by(cyl, disp) |>
  summarise(across(.cols = c(wt,mpg,drat),
                   .fns = mean,na.rm = TRUE,
                   .names = "{col}_mean")
            )
# A tibble: 27 × 5
# Groups:   cyl [3]
     cyl  disp wt_mean mpg_mean drat_mean
   <dbl> <dbl>   <dbl>    <dbl>     <dbl>
 1     4  71.1    1.84     33.9      4.22
 2     4  75.7    1.62     30.4      4.93
 3     4  78.7    2.2      32.4      4.08
 4     4  79      1.94     27.3      4.08
 5     4  95.1    1.51     30.4      3.77
 6     4 108      2.32     22.8      3.85
 7     4 120.     2.46     21.5      3.7 
 8     4 120.     2.14     26        4.43
 9     4 121      2.78     21.4      4.11
10     4 141.     3.15     22.8      3.92
# ℹ 17 more rows

Más dplyr, across()

Podemos transformar cada variable con más de una función nombradas en una lista

mtcars |> 
summarise(across(.cols = c(wt,mpg,drat),
                 .fns = list(mean = mean, sd = sd), 
                 na.rm = TRUE))
  wt_mean     wt_sd mpg_mean   mpg_sd drat_mean   drat_sd
1 3.21725 0.9784574 20.09062 6.026948  3.596563 0.5346787

Más dplyr, across()

En mpg hay variables numéricas y categóricas, si queremos hacer lo mismo para las columnas que son numéricas podemos usar where(is.numeric())

where() es una función de ayuda que selecciona las variables para las que una función retorna TRUE.

mpg |> 
  summarise(across(where(is.numeric), 
                   mean, na.rm = TRUE))

Más dplyr, across()

También se puede escribir como fórmula

mpg |> 
  summarise(across(where(is.numeric),  mean, na.rm = TRUE))

#Equivalente
mpg |> 
  summarise(across(where(is.numeric), ~ mean(.x, na.rm = TRUE)))

Programando con dplyr,

La mayoría de los verbos de dplyr usan tidy evaluations, que es un caso especial de non-standard evaluation usanda en tidyverse dos formas en dplyr:

  • arrange(), count(), filter(), group_by(), mutate(), y summarise() usan data masking entonces uno puede usar variables como si estuvieran en el env, ejemplo usamos mi_var en vez de datos$mi_var.

  • across(), relocate(), rename(), select(), y pull() usan tidy selection podemos seleccionar variables basadas en su posición, nombre o tipo, ejemplo starts_with("x") o is.numeric.

Programando con dplyr,

El principal desafío programando con funciones que usan data masking es cuando queremos obtener la variable de una variable en el env. en vez de poner directamente el nombre de la variable.

Dos casos:

  • Cuando tengo una variable en un argumento de una función ebemos poner el argumento con llaves dobles {{}}. Ejemplo filter(datos, {{ var }}).

  • Cuando tenemos una env-variable que es un vector character. veremos luego

https://dplyr.tidyverse.org/articles/programming.html

Más dplyr, across()

Uso programáticamente

resultado <- function(dato, numeric_cols = NULL, ...) {
  dato |>
    group_by(...) |>
    summarise(across({{numeric_cols}}, list(
      mean = ~mean(.x, na.rm = TRUE),
      sd = ~sd(.x, na.rm = TRUE),
      q05 = ~quantile(.x, 0.05, na.rm = TRUE),
      q95 = ~quantile(.x, 0.95, na.rm = TRUE)
    ), .names = "{col}_{fn}"))
}

resultado(mpg , numeric_cols = c(cty, hwy),
           manufacturer)

Más dplyr

#Versiones anteriores de dplyr con "scoped verbs"
mpg |> 
mutate_if(is.numeric, mean, na.rm = TRUE) |> dim()

#Nuevas versiones de `dplyr`
mpg |>
  mutate(across(where(is.numeric), mean, na.rm = TRUE)

# Diferencia ?

mpg |>
  mutate(across(where(is.numeric), mean, 
                na.rm = TRUE, .names = "{col}_mean")

Explorar vignette("colwise")

Más dplyr

#Versiones anteriores de dplyr con "scoped verbs"
mpg |> 
select_if(is.numeric) |> dim()

#Nuevas versiones de `dplyr`
mpg |>
  select(where(is.numeric))|> dim()

Estructurar datos

  • Lograr que tus datos estén en una forma adecuada para la visualización y modelado.

  • Sin estructurar los datos muchas veces es imposible trabajar con ellos.

Estructurar datos

Componentes claves:

  • tibbles son una variante de los data.frames.

  • Importar datos, leer datos en distintos formatos.

  • Datos limpios, una forma consistente de guardar tus datos para que sea más sencillo transformar, visualizar y modelar (¿qué es tidy data?).

Vamos a empezar por datos limpios

  • A que le llamamos datos limpios (tidy data), nos concentraremos en el paquete tidyr de tidyverse.

  • Ordenando usando pivol_longer() y pivot_wider().

  • Es importante aprender como organizar nuestros datos en una forma consistente (tidy data)

Posibles problemas de los datos

  • Los nombres de las columnas son valores no nombres de variables.

  • Muchas variables están ordenadas en una sola columna.

  • Muchas unidades observacionales están ordenadas en la misma tabla.

  • Una sola unidad observacional está en muchas tablas.

¿Qué son datos limpios? (tidy data)

  • Cada variable forma una columna

  • Cada observación forma una fila

  • Cada valor debe estar en una celda

Qué son datos limpios? (tidy data)

Diferentes formas de los mismos datos

Ejemplo del paper Tidy data: paper tidy data

Importancia de los datos limpios

Ventajas:

  • Si tenés una forma consistente de guardar los datos es más sencillo aprender las herramientas que funcionan con la misma

  • Tener la variables en columnas son naturalmente manejadas en la forma vectorizada de R. La mayoría de las funciones de R funcionan con valores vectorizados. Esto hace natural transformar la estructura de datos a una limpia (tidy data).

Trágica realidad

Los datos en general no están limpios así que tenemos que aprender a limpiar!

¿Porqué los datos no están limpios? - La gente no esta familiarizada con el concepto de tidy data y no es sencillo derivarlo solo.

  • Los datos están a menudo organizados para simplificar algo que no necesariamente es análisis de datos.

Pasos para limpiar!

  • Identificar cuales son variables y cuales observaciones.

  • Identificar si hay variables que deben ser dispuestas en múltiples columnas.

  • Identificar si una observación debe ser incluida en múltiples filas.

Verbos de datos limpios pkg tidyr

  • pivot_longer() reestructura los datos moviendo columnas a filas.

  • pivot_wider() reestructura los datos moviendo filas a columnas.

  • separate divide columnas en múltiples variables.

pivot_longer()

Problema común: nombres de columnas no representan nombres de variable, como debería ser, sino que son valores de variables.

Se puede resolver con pivot_longer().

pivot_longer()

Genero datos con tibble

library(tidyverse)
medico <- tibble(nombre = c("Pablo Gomez", "Mariana Mattos",
                           "Laura Almo"),
                 tratamientoa = c(NA, 4, 6),
                tratamientob = c(18, 1, 7))
         
medico
# A tibble: 3 × 3
  nombre         tratamientoa tratamientob
  <chr>                 <dbl>        <dbl>
1 Pablo Gomez              NA           18
2 Mariana Mattos            4            1
3 Laura Almo                6            7

pivot_longer()

Necesitamos unir (pivot) estas columnas en un nuevo par de variables. Necesitamos describir tres argumentos:

  • Las columnas cuyos nombres son valores no variables, en este caso tratamientoa y tratamientob, argumento cols

  • Un nombre para la variable cuyo valor está en el nombre de la columna, en este caso le podemos llamar tratamiento, argumento names_to

  • Un nombre para la variable cuyos valores están en las celdas, en este caso le podemos llamar casos, argumento values_to

Reestructuro con pivot_longer()

pivot_longer(data, cols, names_to, values_to,...)

  • data: data.frame a pivotar

  • cols: Columnas a pivotar en formato largo

  • names_to: un vector de tipo character que especifica el nombre de la columna o columnas a crear con los datos contenidos en el nombre de la columna

  • values_to: un string especificando el nombre de la columna a crear con los datos contenidos en las celdas

  • …: otros argumentos

Reestructuro con pivot_longer()

  • Problema: nombres de las columnas son valores
  • Tenemos que transformar columnas en filas, uso pivot_longer()
medico |> 
pivot_longer(cols = c("tratamientoa", "tratamientob"),
             names_to = "tratamiento" , values_to = "conteo") 

Reestructuro con pivot_longer()

Resultados equivalentes con pivot_longer()

medico |> 
pivot_longer(cols = starts_with("tratamiento"),
             names_to ="tratamiento" , values_to = "conteo") 


medico |> 
pivot_longer(cols = 2:3,
             names_to ="tratamiento" , values_to = "conteo") 

medico |> 
pivot_longer(cols = !nombre, 
             names_to ="tratamiento" , values_to = "conteo") 

Las columnas a girar se especifican con el estilo de notación usado en select() (tipo, nombre y posición )

Reestructuro con pivot_longer()

pivotmed <- medico |>
pivot_longer(cols = c("tratamientoa", "tratamientob"),
             names_to ="tratamiento" , values_to = "conteo") 

pivotmed
# A tibble: 6 × 3
  nombre         tratamiento  conteo
  <chr>          <chr>         <dbl>
1 Pablo Gomez    tratamientoa     NA
2 Pablo Gomez    tratamientob     18
3 Mariana Mattos tratamientoa      4
4 Mariana Mattos tratamientob      1
5 Laura Almo     tratamientoa      6
6 Laura Almo     tratamientob      7

Reestructuro con pivot_longer()

Podríamos combinar con lo que aprendimos con dplyr para recodificar tratamiento

pivotmed <-medico |>
pivot_longer(cols = c("tratamientoa", "tratamientob"),
             names_to ="tratamiento" , values_to = "conteo") |> 
  mutate(tratamiento =  case_match(tratamiento, "tratamientoa" ~ "a",
                               "tratamientob" ~ "b"))


pivotmed
# A tibble: 6 × 3
  nombre         tratamiento conteo
  <chr>          <chr>        <dbl>
1 Pablo Gomez    a               NA
2 Pablo Gomez    b               18
3 Mariana Mattos a                4
4 Mariana Mattos b                1
5 Laura Almo     a                6
6 Laura Almo     b                7

Reestructuro con pivot_wider()

  • pivot_wider() es la operación opuesta a pivot_longer() se usa cuando las observaciones están dispersas en muchas filas. Incrementa el número de columnas y disminuye el de las filas.

pivot_wider(data, names_from, values_from, ...)

  • names_from : La columna que tiene en las observaciones el nombre de las nuevas columnas

  • values_from:La columna que tiene los valores

  • ...: otros argumentos

Reestructuro con pivot_wider()

Datos que quiero reestructurar, quiero poner los tratamientos a y b en las columnas

pivotmed
# A tibble: 6 × 3
  nombre         tratamiento conteo
  <chr>          <chr>        <dbl>
1 Pablo Gomez    a               NA
2 Pablo Gomez    b               18
3 Mariana Mattos a                4
4 Mariana Mattos b                1
5 Laura Almo     a                6
6 Laura Almo     b                7

Reestructuro con pivot_wider()

pivotmed |> 
  pivot_wider(names_from = tratamiento, values_from = conteo)
# A tibble: 3 × 3
  nombre             a     b
  <chr>          <dbl> <dbl>
1 Pablo Gomez       NA    18
2 Mariana Mattos     4     1
3 Laura Almo         6     7

separate()

  • separate separa una columna en múltiples columnas. Por defecto cualquier símbolo no alfanumérico se usa para separar la columna,

Principales parámetros

  • data: data.frame.

  • col: el nombre de la columna o posición.

  • into: nombres de las nuevas variables para crear, vector tipo character.

  • sep: separador entre columnas

Formalmente si es de tipo character, sep es una expresión regular que veremos en más detalle luego. Si es numeric, sep es interpretado como una posición para separar contando de izquierda a derecha arranca en 1 y de derecha a izquierda de -1.

separate()

tib <- tibble(x =c(NA, "a*b", "a.c", "a&d"),
              month_year = c(121999, 112005, 102001, 102001) ) 
tib
# A tibble: 4 × 2
  x     month_year
  <chr>      <dbl>
1 <NA>      121999
2 a*b       112005
3 a.c       102001
4 a&d       102001
tib |> separate(col = x, into = c("V1", "V2"))
# A tibble: 4 × 3
  V1    V2    month_year
  <chr> <chr>      <dbl>
1 <NA>  <NA>      121999
2 a     b         112005
3 a     c         102001
4 a     d         102001

separate()

tib |> separate(col = month_year, 
                 into = c("month", "year"), sep = 2)
# A tibble: 4 × 3
  x     month year 
  <chr> <chr> <chr>
1 <NA>  12    1999 
2 a*b   11    2005 
3 a.c   10    2001 
4 a&d   10    2001 

separate()

¿Qué pasa con la clase de las nuevas variables en tib2?

tib2 <- tibble(x =c(NA, "1*b", "3.c", "8&d"),
              month_year = c(121999, 112005, 102001, 102001) ) 
tib2
# A tibble: 4 × 2
  x     month_year
  <chr>      <dbl>
1 <NA>      121999
2 1*b       112005
3 3.c       102001
4 8&d       102001
tib2 |> separate(col = x, into = c("V1", "V2"))
# A tibble: 4 × 3
  V1    V2    month_year
  <chr> <chr>      <dbl>
1 <NA>  <NA>      121999
2 1     b         112005
3 3     c         102001
4 8     d         102001

separate()

Deja por defecto el tipo original y en algunos casos no es lo correcto. Uso convert = TRUE lo intenta convertir a un tipo más apropiado.

tib2 <- tibble(x =c(NA, "1*b", "3.c", "8&d"),
              month_year = c(121999, 112005, 102001, 102001) ) 
tib2
# A tibble: 4 × 2
  x     month_year
  <chr>      <dbl>
1 <NA>      121999
2 1*b       112005
3 3.c       102001
4 8&d       102001
tib2 |> separate(col = x, into = c("V1", "V2"), convert = TRUE)
# A tibble: 4 × 3
     V1 V2    month_year
  <int> <chr>      <dbl>
1    NA <NA>      121999
2     1 b         112005
3     3 c         102001
4     8 d         102001

unite()

unite() es el inverso de separate(). Pega múltiples columnas en una.

  • unite(data, col, ...)
tib3 <- tib |> separate(month_year, into = c("month", "year"),
                         sep = 2)

tib3 |> 
  unite(col = v1, month, year )
# A tibble: 4 × 2
  x     v1     
  <chr> <chr>  
1 <NA>  12_1999
2 a*b   11_2005
3 a.c   10_2001
4 a&d   10_2001

unite()

tib3 |> 
  unite(col = v1, month, year, sep = "/" )
# A tibble: 4 × 2
  x     v1     
  <chr> <chr>  
1 <NA>  12/1999
2 a*b   11/2005
3 a.c   10/2001
4 a&d   10/2001

Tu turno, Actividad 3